feat: add Abnormal Security partner integration#682
feat: add Abnormal Security partner integration#682anoopabsec wants to merge 3 commits intochronicle:mainfrom
Conversation
Adds the Abnormal Security response integration for Google SecOps SOAR. This integration enables automated email threat search and remediation via the Abnormal Security API. Actions: - Ping: Test connectivity and authentication - Search Messages: Find email threats by time range, sender, or subject - Remediate Messages: Delete/move/reclassify malicious messages - Get Activity Status: Poll remediation operation status Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a new third-party integration for Google SecOps SOAR, allowing users to interact with the Abnormal Security API. The changes include the necessary configuration, core API management logic, and a suite of actions to support automated email security workflows, such as searching for threats and executing remediation actions. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 4282490. Configure here.
| [tool.uv.sources] | ||
| tipcommon = { path = "../../../../../packages/tipcommon/whls/TIPCommon-1.1.0.1-py2.py3-none-any.whl" } | ||
| environmentcommon = { path = "../../../../../packages/envcommon/whls/EnvironmentCommon-1.0.2-py2.py3-none-any.whl" } | ||
| soar-sdk = { path = "/Users/anoop/soar-sdk" } |
There was a problem hiding this comment.
Local developer path committed in pyproject.toml
High Severity
The soar-sdk source in pyproject.toml points to a hardcoded local path /Users/anoop/soar-sdk, and uv.lock also contains this path. Other integrations in the repo (e.g., anyrun_sandbox) correctly use soar-sdk = { git = "https://github.com/chronicle/soar-sdk.git" }. This will break builds for any other developer or CI environment.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 4282490. Configure here.
| ) | ||
|
|
||
| messages = [m.strip() for m in messages_raw.split(",") if m.strip()] | ||
| tenant_ids = [t.strip() for t in tenant_ids_raw.split(",")] if tenant_ids_raw else None |
There was a problem hiding this comment.
Inconsistent tenant ID parsing allows empty strings
Medium Severity
The tenant_ids parsing in RemediateMessages.py and SearchMessages.py does not filter out empty strings from the split result (e.g., "id1,,id2" produces ["id1", "", "id2"]). In contrast, GetActivityStatus.py correctly uses if t.strip() to filter them out. Empty strings in the tenant IDs list would be sent to the API, potentially causing errors or unexpected behavior.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 4282490. Configure here.
| ) | ||
|
|
||
| messages = response.get("messages", []) | ||
| siemplify.result.add_result_json(json.dumps(response)) |
There was a problem hiding this comment.
Double JSON encoding in result output
Medium Severity
add_result_json is called with json.dumps(response) (a pre-serialized string), but other integrations in the repo (e.g., censys) pass raw Python objects directly. If add_result_json performs its own serialization internally, this produces double-encoded JSON — downstream consumers would receive a JSON string literal instead of a structured object.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 4282490. Configure here.
There was a problem hiding this comment.
Code Review
This pull request implements a new Abnormal Security integration for Google SecOps SOAR, providing actions for connectivity testing, message searching, remediation, and status tracking. The feedback highlights several areas for improvement to meet repository standards, including the addition of mandatory unit tests, pinning the Python version range, and refactoring the API manager to use asynchronous I/O. Furthermore, corrections are required for standardized Ping action messages, proper TIPCommon submodule imports, and the prevention of double-encoded JSON results in action outputs. Ensuring all functions have complete docstrings and type annotations is also necessary for compliance.
| @@ -0,0 +1,26 @@ | |||
| [project] | |||
There was a problem hiding this comment.
This pull request is missing the required unit tests. The repository style guide is very strict that all new features, bug fixes, or integrations added to the content/response_integrations/** directory must include corresponding unit tests. Please add a tests/ directory with pytest tests for the new actions and manager, modeling them after the examples in content/response_integrations/third_party/telegram/tests/.
References
- All new features, bug fixes, or integrations added to
content/response_integrations/**must include corresponding unit tests to ensure production stability. (link)
| output_message = ( | ||
| f"Successfully connected to the {INTEGRATION_DISPLAY_NAME} API at {api_url}." | ||
| ) |
There was a problem hiding this comment.
The success message for the Ping action does not conform to the exact format required by the repository style guide. Using the standard format ensures consistent and predictable output for playbook designers.
| output_message = ( | |
| f"Successfully connected to the {INTEGRATION_DISPLAY_NAME} API at {api_url}." | |
| ) | |
| output_message = ( | |
| f"Successfully connected to the {INTEGRATION_DISPLAY_NAME} server with the provided " | |
| f"connection parameters!" | |
| ) |
References
- The Ping action success message must be exactly: "Successfully connected to the {integration name} server with the provided connection parameters!" (link)
| ) | ||
|
|
||
| activity_status = response.get("status", "unknown") | ||
| siemplify.result.add_result_json(json.dumps(response)) |
There was a problem hiding this comment.
The add_result_json method handles the JSON serialization internally. By passing json.dumps(response), you are creating a double-encoded JSON string, which will likely cause issues in the playbook. Please pass the dictionary object directly.
| siemplify.result.add_result_json(json.dumps(response)) | |
| siemplify.result.add_result_json(response) |
References
- If an action returns a JSON result by calling
result.add_result_json(...), it must be a valid JSON structure. Double-encoding can break playbook placeholders. (link)
| ) | ||
|
|
||
| messages = response.get("messages", []) | ||
| siemplify.result.add_result_json(json.dumps(response)) |
There was a problem hiding this comment.
The add_result_json method handles the JSON serialization internally. By passing json.dumps(response), you are creating a double-encoded JSON string, which will likely cause issues in the playbook. Please pass the dictionary object directly.
| siemplify.result.add_result_json(json.dumps(response)) | |
| siemplify.result.add_result_json(response) |
References
- If an action returns a JSON result by calling
result.add_result_json(...), it must be a valid JSON structure. Double-encoding can break playbook placeholders. (link)
| ) | ||
|
|
||
| activity_log_id = response.get("activity_log_id", "") | ||
| siemplify.result.add_result_json(json.dumps(response)) |
There was a problem hiding this comment.
The add_result_json method handles the JSON serialization internally. By passing json.dumps(response), you are creating a double-encoded JSON string, which will likely cause issues in the playbook. Please pass the dictionary object directly.
| siemplify.result.add_result_json(json.dumps(response)) | |
| siemplify.result.add_result_json(response) |
References
- If an action returns a JSON result by calling
result.add_result_json(...), it must be a valid JSON structure. Double-encoding can break playbook placeholders. (link)
|
|
||
|
|
||
| @output_handler | ||
| def main(): |
There was a problem hiding this comment.
The main function is missing a docstring and a return type annotation, which is required by the repository style guide. All functions must have docstrings and be fully type-annotated.
| def main(): | |
| @output_handler | |
| def main() -> None: | |
| """ | |
| Main execution logic for the Search Messages action. | |
| """ |
References
- All function parameters and return types must be annotated. Use triple double quotes for all function docstrings. (link)
| except AbnormalValidationError as e: | ||
| output_message = f"Invalid parameters: {e}" | ||
| siemplify.LOGGER.error(output_message) | ||
|
|
||
| except AbnormalAuthenticationError as e: | ||
| output_message = f"Authentication failed: {e}" | ||
| siemplify.LOGGER.error(output_message) | ||
|
|
||
| except AbnormalConnectionError as e: | ||
| output_message = f"Connection error: {e}" | ||
| siemplify.LOGGER.error(output_message) | ||
|
|
||
| except Exception as e: | ||
| output_message = f"An unexpected error occurred: {e}" | ||
| siemplify.LOGGER.error(output_message) | ||
| siemplify.LOGGER.exception(e) |
There was a problem hiding this comment.
The error messages in the except blocks do not follow the standard format specified in the style guide. For consistency in playbook outputs, please prefix error messages with Error executing action "{action name}". Reason: {error}.
| except AbnormalValidationError as e: | |
| output_message = f"Invalid parameters: {e}" | |
| siemplify.LOGGER.error(output_message) | |
| except AbnormalAuthenticationError as e: | |
| output_message = f"Authentication failed: {e}" | |
| siemplify.LOGGER.error(output_message) | |
| except AbnormalConnectionError as e: | |
| output_message = f"Connection error: {e}" | |
| siemplify.LOGGER.error(output_message) | |
| except Exception as e: | |
| output_message = f"An unexpected error occurred: {e}" | |
| siemplify.LOGGER.error(output_message) | |
| siemplify.LOGGER.exception(e) | |
| except AbnormalValidationError as e: | |
| output_message = f'Error executing action "{siemplify.script_name}". Reason: {e}' | |
| siemplify.LOGGER.error(output_message) | |
| except AbnormalAuthenticationError as e: | |
| output_message = f'Error executing action "{siemplify.script_name}". Reason: {e}' | |
| siemplify.LOGGER.error(output_message) | |
| except AbnormalConnectionError as e: | |
| output_message = f'Error executing action "{siemplify.script_name}". Reason: {e}' | |
| siemplify.LOGGER.error(output_message) | |
| except Exception as e: | |
| output_message = f'Error executing action "{siemplify.script_name}". Reason: An unexpected error occurred: {e}' | |
| siemplify.LOGGER.error(output_message) | |
| siemplify.LOGGER.exception(e) |
References
- Error messages should follow a standard template for consistency. The required prefix is
Error executing action "{action name}". Reason: {error}. (link)
| """Raised when input validation fails.""" | ||
|
|
||
|
|
||
| class AbnormalManager: |
There was a problem hiding this comment.
The style guide recommends using asyncio with httpx or aiohttp for I/O-bound operations like network requests. This manager uses the synchronous requests library. While functional, refactoring to an asynchronous implementation would better align with the repository's modern Python patterns and improve performance in high-throughput SecOps workflows.
References
- Since many SecOps workflows are I/O bound (API calls, logs), we leverage
asyncio. Useasyncandawaitfor network requests (e.g., usinghttpxoraiohttp). (link)
|
|
||
|
|
||
| @output_handler | ||
| def main(): |
There was a problem hiding this comment.
The main function is missing a docstring and a return type annotation, which is required by the repository style guide. All functions must have docstrings and be fully type-annotated.
| def main(): | |
| @output_handler | |
| def main() -> None: | |
| """ | |
| Main execution logic for the Get Activity Status action. | |
| """ |
References
- All function parameters and return types must be annotated. Use triple double quotes for all function docstrings. (link)
| name = "AbnormalSecurity" | ||
| version = "1.0" | ||
| description = "Abnormal Security uses AI to protect organizations from email attacks. This integration enables automated search and remediation of malicious email messages through the Abnormal Security API. For support, contact: support@abnormalsecurity.com" | ||
| requires-python = ">=3.11" |
There was a problem hiding this comment.
The requires-python value does not match the repository's standard. To ensure compatibility and prevent unexpected issues from future Python minor releases, please pin the upper version.
| requires-python = ">=3.11" | |
| requires-python = ">=3.11,<3.12" |
References
- The
requires-pythondependency inpyproject.tomlshould be pinned to a specific minor version range, e.g.,">=3.11,<3.12". (link)
|
You have used all of your free Bugbot PR reviews. To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial. |
|
❌ Marketplace Validation Failed Click to view the full reportValidation Report🧩 IntegrationsPre-Build Stageabnormal_security
|


Summary
Adds the Abnormal Security response integration for Google SecOps SOAR, enabling automated email threat search and remediation through the Abnormal Security API.
Changes
definition.yaml): API URL, API Key, Verify SSL configuration parameterscore/AbnormalManager.py): HTTP client with Bearer token auth, automatic retry on 429/5xx, custom exceptionsActivity Log IDsuccess/failed/completed)pyproject.toml,uv.lock,release_notes.yaml,ontology_mapping.yaml, JSON result examples, SVG logoTest Plan
mp validate integration abnormal_security— 22/22 validations passedmp build integration abnormal_security— builds successfullyDeploy Plan
No relevant deploy plans — this is a new marketplace integration submitted via PR for Google SecOps review.
🤖 Generated with Claude Code
Note
Medium Risk
Introduces a new integration that can perform message remediation via an external API, so mistakes in request construction/validation could lead to unintended email actions. Impact is mostly isolated to the new
abnormal_securityintegration package.Overview
Adds a new
AbnormalSecurityresponse integration, including configuration (API URL,API Key,Verify SSL) and packaging metadata (pyproject.toml,uv.lock,release_notes.yaml).Implements four new SOAR actions—
Ping,Search Messages,Remediate Messages, andGet Activity Status—backed by a newAbnormalManagerHTTP client with Bearer auth, retry handling for 429/5xx, and input validation for remediation/status parameters, plus example JSON results and branding resources.Reviewed by Cursor Bugbot for commit 4282490. Bugbot is set up for automated code reviews on this repo. Configure here.